package hawk.example.bookstore.sql.provider;

import static glz.hawkframework.mybatis.writer.MybatisBuilderContextImpl.*;
import static glz.hawkframework.sql.support.impl.DSL.*;
import static glz.hawkframework.support.ArgumentSupport.*;
import static glz.hawkframework.support.LogicSupport.*;
import static hawk.example.bookstore.support.BookSupport.*;

import glz.hawkframework.helper.ObjectHelper;
import glz.hawkframework.mybatis.sql.MybatisParam;
import glz.hawkframework.mybatis.sql.provider.AbstractSqlProvider;
import glz.hawkframework.mybatis.statement.GeneralStatementProvider;
import glz.hawkframework.mybatis.statement.InsertStatementProvider;
import glz.hawkframework.mybatis.statement.MultiRowsInsertStatementProvider;
import glz.hawkframework.mybatis.statement.UpdateStatementProvider;
import glz.hawkframework.mybatis.statement.impl.GeneralStatementProviderImpl;
import glz.hawkframework.mybatis.statement.impl.InsertStatementProviderImpl;
import glz.hawkframework.mybatis.statement.impl.MultiRowsInsertStatementProviderImpl;
import glz.hawkframework.mybatis.statement.impl.UpdateStatementProviderImpl;
import glz.hawkframework.mybatis.writer.MybatisBuilderContext;
import glz.hawkframework.sql.condition.Condition;
import glz.hawkframework.sql.dsl.delete.Delete;
import glz.hawkframework.sql.dsl.insert.Insert;
import glz.hawkframework.sql.dsl.select.Select;
import glz.hawkframework.sql.dsl.update.Update;
import glz.hawkframework.sql.support.SqlBuilder;
import glz.hawkframework.sql.support.impl.DefaultAliasedNamedColumn;
import glz.hawkframework.sql.util.QueryWrapper;

import hawk.example.bookstore.po.BookPo;
import hawk.example.bookstore.update.BookColumnUpdate;
import hawk.example.bookstore.update.BookUpdate;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.ibatis.builder.annotation.ProviderMethodResolver;
import org.springframework.stereotype.Component;

@Component
public class BookSqlProvider extends AbstractSqlProvider implements ProviderMethodResolver {
    public BookSqlProvider(SqlBuilder<MybatisBuilderContext> sqlBuilder) {
        super(sqlBuilder);
    }

    protected Insert buildInsert() {
        return insertInto(BOOK, COLUMNS).values(Arrays.stream(PARAM_COLUMNS).map(p->p.prefix(InsertStatementProvider.PARAM_PREFIX)).toArray()).build();
    }

    public InsertStatementProvider<BookPo> insert(BookPo bookPo) {
        String key = buildKey("insert", BookPo.class);
        String insertSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildInsert(), DUMMY_INSTANCE));
        return InsertStatementProviderImpl.builder(insertSql, bookPo).build();
    }

    protected Insert buildInsertSelective(BookPo bookPo) {
        List<DefaultAliasedNamedColumn> columns = new ArrayList<>();
        List<MybatisParam> params = new ArrayList<>();
        if (bookPo.getBookId() != null) {columns.add(BOOK_ID); params.add(PARAM_BOOK_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getIsbn() != null) {columns.add(ISBN); params.add(PARAM_ISBN.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getTitle() != null) {columns.add(TITLE); params.add(PARAM_TITLE.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getPrice() != null) {columns.add(PRICE); params.add(PARAM_PRICE.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getPublishDate() != null) {columns.add(PUBLISH_DATE); params.add(PARAM_PUBLISH_DATE.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getBookCategory() != null) {columns.add(BOOK_CATEGORY); params.add(PARAM_BOOK_CATEGORY.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getPublisherId() != null) {columns.add(PUBLISHER_ID); params.add(PARAM_PUBLISHER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getCreateDateTime() != null) {columns.add(CREATE_DATE_TIME); params.add(PARAM_CREATE_DATE_TIME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getUpdateDateTime() != null) {columns.add(UPDATE_DATE_TIME); params.add(PARAM_UPDATE_DATE_TIME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getDeleteDateTime() != null) {columns.add(DELETE_DATE_TIME); params.add(PARAM_DELETE_DATE_TIME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getCreateUserId() != null) {columns.add(CREATE_USER_ID); params.add(PARAM_CREATE_USER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getUpdateUserId() != null) {columns.add(UPDATE_USER_ID); params.add(PARAM_UPDATE_USER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getDeleteUserId() != null) {columns.add(DELETE_USER_ID); params.add(PARAM_DELETE_USER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (bookPo.getRecordVersion() != null) {columns.add(RECORD_VERSION); params.add(PARAM_RECORD_VERSION.prefix(InsertStatementProvider.PARAM_PREFIX));}
        return insertInto(BOOK, columns.toArray(new DefaultAliasedNamedColumn[0])).values(params.toArray(new MybatisParam[0])).build();
    }

    public InsertStatementProvider<BookPo> insertSelective(BookPo bookPo) {
        String insertSql = sqlBuilder.build(buildInsertSelective(bookPo), DUMMY_INSTANCE);
        return InsertStatementProviderImpl.builder(insertSql, bookPo).build();
    }

    protected Insert buildInsertMultiple(List<BookPo> bookPos) {
        MybatisParam[][] paramses = new MybatisParam[bookPos.size()][14];
        for (int i = 0; i < bookPos.size(); i++) {
            MybatisParam[] params = new MybatisParam[14];
            params[0] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_BOOK_ID.getParamName()) , PARAM_BOOK_ID.getJdbcType()).build();
            params[1] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_ISBN.getParamName()) , PARAM_ISBN.getJdbcType()).build();
            params[2] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_TITLE.getParamName()) , PARAM_TITLE.getJdbcType()).build();
            params[3] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_PRICE.getParamName()) , PARAM_PRICE.getJdbcType()).build();
            params[4] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_PUBLISH_DATE.getParamName()) , PARAM_PUBLISH_DATE.getJdbcType()).build();
            params[5] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_BOOK_CATEGORY.getParamName()) , PARAM_BOOK_CATEGORY.getJdbcType()).build();
            params[6] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_PUBLISHER_ID.getParamName()) , PARAM_PUBLISHER_ID.getJdbcType()).build();
            params[7] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_CREATE_DATE_TIME.getParamName()) , PARAM_CREATE_DATE_TIME.getJdbcType()).build();
            params[8] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_UPDATE_DATE_TIME.getParamName()) , PARAM_UPDATE_DATE_TIME.getJdbcType()).build();
            params[9] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_DELETE_DATE_TIME.getParamName()) , PARAM_DELETE_DATE_TIME.getJdbcType()).build();
            params[10] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_CREATE_USER_ID.getParamName()) , PARAM_CREATE_USER_ID.getJdbcType()).build();
            params[11] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_UPDATE_USER_ID.getParamName()) , PARAM_UPDATE_USER_ID.getJdbcType()).build();
            params[12] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_DELETE_USER_ID.getParamName()) , PARAM_DELETE_USER_ID.getJdbcType()).build();
            params[13] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_RECORD_VERSION.getParamName()) , PARAM_RECORD_VERSION.getJdbcType()).build();
            paramses[i] = params;
        }
        return insertInto(BOOK, COLUMNS).valueses(paramses).build();
    }

    public MultiRowsInsertStatementProvider<BookPo> insertMultiple(List<BookPo> bookPos) {
        String insertSql = sqlBuilder.build(buildInsertMultiple(bookPos), DUMMY_INSTANCE);
        return MultiRowsInsertStatementProviderImpl.builder(insertSql, bookPos).build();
    }

    public static Delete buildDeleteByPrimaryKey() {
        return deleteFrom(BOOK).where(BOOK_ID.eq(PARAM_BOOK_ID.prefix(GeneralStatementProvider.PARAM_PREFIX)), and(RECORD_VERSION.eq(PARAM_RECORD_VERSION.prefix(GeneralStatementProvider.PARAM_PREFIX)))).build();
    }

    public GeneralStatementProvider deleteByPrimaryKey(BigInteger bookId, Long recordVersion) {
        String key = buildKey("deleteByPrimaryKey", BigInteger.class, Long.class);
        String deleteSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildDeleteByPrimaryKey(), DUMMY_INSTANCE));
        return GeneralStatementProviderImpl.builder(deleteSql).addParam("bookId", argNotNull(bookId, "bookId")).addParam("recordVersion", argNotNull(recordVersion, "recordVersion")).build();
    }

    protected Update buildUpdateByPrimaryKey(BookUpdate bookUpdate) {
        return update(BOOK)
            .set(c -> consumeIfTrue(bookUpdate::isBookIdUpdated, () -> c.set(BOOK_ID, PARAM_BOOK_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isIsbnUpdated, () -> c.set(ISBN, PARAM_ISBN.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isTitleUpdated, () -> c.set(TITLE, PARAM_TITLE.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isPriceUpdated, () -> c.set(PRICE, PARAM_PRICE.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isPublishDateUpdated, () -> c.set(PUBLISH_DATE, PARAM_PUBLISH_DATE.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isBookCategoryUpdated, () -> c.set(BOOK_CATEGORY, PARAM_BOOK_CATEGORY.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isPublisherIdUpdated, () -> c.set(PUBLISHER_ID, PARAM_PUBLISHER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isCreateDateTimeUpdated, () -> c.set(CREATE_DATE_TIME, PARAM_CREATE_DATE_TIME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isUpdateDateTimeUpdated, () -> c.set(UPDATE_DATE_TIME, PARAM_UPDATE_DATE_TIME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isDeleteDateTimeUpdated, () -> c.set(DELETE_DATE_TIME, PARAM_DELETE_DATE_TIME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isCreateUserIdUpdated, () -> c.set(CREATE_USER_ID, PARAM_CREATE_USER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isUpdateUserIdUpdated, () -> c.set(UPDATE_USER_ID, PARAM_UPDATE_USER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isDeleteUserIdUpdated, () -> c.set(DELETE_USER_ID, PARAM_DELETE_USER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(bookUpdate::isRecordVersionUpdated, () -> c.set(RECORD_VERSION, PARAM_RECORD_VERSION.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .where(BOOK_ID.eq(PARAM_BOOK_ID.prefix(UpdateStatementProvider.UPDATE_CONDITION_PREFIX)), and(RECORD_VERSION.eq(PARAM_RECORD_VERSION.prefix(UpdateStatementProvider.UPDATE_CONDITION_PREFIX))))
            .build();
    }

    public UpdateStatementProvider updateByPrimaryKey(BookUpdate bookUpdate, BigInteger bookId, Long recordVersion) {
        String updateSql = sqlBuilder.build(buildUpdateByPrimaryKey(bookUpdate), DUMMY_INSTANCE);
        return UpdateStatementProviderImpl.builder(updateSql, bookUpdate).addParam("bookId", argNotNull(bookId, "bookId")).addParam("recordVersion", argNotNull(recordVersion, "recordVersion")).build();
    }

    protected Select buildSelectByPrimaryKey() {
        return select(COLUMNS).from(BOOK).where(BOOK_ID.eq(PARAM_BOOK_ID.prefix(GeneralStatementProvider.PARAM_PREFIX))).build();
    }

    public GeneralStatementProvider selectByPrimaryKey(BigInteger bookId) {
        String key = buildKey("selectByPrimaryKey", BigInteger.class);
        String selectSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildSelectByPrimaryKey(), DUMMY_INSTANCE));
        return GeneralStatementProviderImpl.builder(selectSql).addParam("bookId", argNotNull(bookId, "bookId")).build();
    }

    protected Select buildCuntByPrimaryKey() {
        return selectCount().from(BOOK).where(BOOK_ID.eq(PARAM_BOOK_ID.prefix(GeneralStatementProvider.PARAM_PREFIX))).build();
    }

    public GeneralStatementProvider countByPrimaryKey(BigInteger bookId) {
        String key = buildKey("countByPrimaryKey", BigInteger.class);
        String selectSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildCuntByPrimaryKey(), DUMMY_INSTANCE));
        return GeneralStatementProviderImpl.builder(selectSql).addParam("bookId", argNotNull(bookId, "bookId")).build();
    }

    public static Select buildSelectOrCountDynamic(QueryWrapper queryWrapper) {
        return select(queryWrapper.isCount(), queryWrapper.isDistinct(), ObjectHelper.isEmpty(queryWrapper.getColumns()) ? COLUMNS : queryWrapper.getColumns())
            .from(BOOK)
            .where(queryWrapper.getCondition())
            .orderBy(ObjectHelper.isNotEmpty(queryWrapper.getOrderColumns()), queryWrapper.getOrderColumns())
            .limit(queryWrapper.getLimit() != null && !queryWrapper.isCount(), queryWrapper.getLimit())
            .offset(queryWrapper.getOffset() != null && !queryWrapper.isCount(), queryWrapper.getOffset())
            .forUpdate(queryWrapper.isForUpdate() && !queryWrapper.isCount())
            .build();
    }

    public GeneralStatementProvider selectOrCountDynamic(QueryWrapper queryWrapper) {
        return generalSelect(buildSelectOrCountDynamic(queryWrapper));
    }

    public static Delete buildDeleteDynamic(Condition condition) {
        return deleteFrom(BOOK).where(condition).build();
    }

    public GeneralStatementProvider deleteDynamic(Condition condition) {
        return generalDelete(buildDeleteDynamic(condition));
    }

    public static Update buildUpdateDynamic(BookColumnUpdate bookColumnUpdate, Condition condition) {
        return update(BOOK)
            .set(c -> consumeIfTrue(bookColumnUpdate::isBookIdUpdated, () -> c.set(BOOK_ID, bookColumnUpdate.getBookId())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isIsbnUpdated, () -> c.set(ISBN, bookColumnUpdate.getIsbn())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isTitleUpdated, () -> c.set(TITLE, bookColumnUpdate.getTitle())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isPriceUpdated, () -> c.set(PRICE, bookColumnUpdate.getPrice())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isPublishDateUpdated, () -> c.set(PUBLISH_DATE, bookColumnUpdate.getPublishDate())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isBookCategoryUpdated, () -> c.set(BOOK_CATEGORY, bookColumnUpdate.getBookCategory())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isPublisherIdUpdated, () -> c.set(PUBLISHER_ID, bookColumnUpdate.getPublisherId())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isCreateDateTimeUpdated, () -> c.set(CREATE_DATE_TIME, bookColumnUpdate.getCreateDateTime())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isUpdateDateTimeUpdated, () -> c.set(UPDATE_DATE_TIME, bookColumnUpdate.getUpdateDateTime())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isDeleteDateTimeUpdated, () -> c.set(DELETE_DATE_TIME, bookColumnUpdate.getDeleteDateTime())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isCreateUserIdUpdated, () -> c.set(CREATE_USER_ID, bookColumnUpdate.getCreateUserId())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isUpdateUserIdUpdated, () -> c.set(UPDATE_USER_ID, bookColumnUpdate.getUpdateUserId())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isDeleteUserIdUpdated, () -> c.set(DELETE_USER_ID, bookColumnUpdate.getDeleteUserId())))
            .set(c -> consumeIfTrue(bookColumnUpdate::isRecordVersionUpdated, () -> c.set(RECORD_VERSION, bookColumnUpdate.getRecordVersion())))
            .where(condition)
            .build();
    }

    public GeneralStatementProvider updateDynamic(BookColumnUpdate bookColumnUpdate, Condition condition) {
        return generalUpdate(buildUpdateDynamic(bookColumnUpdate, condition));
    }
}
